<?php

namespace app\api\service;

use app\model\Money;
use app\model\RefundRecord;
use app\common\Error;
use app\model\User;
use app\common\Common;
use app\model\PayRecord;
use think\Exception;
use think\facade\Db;
use app\model\OfficialAccounts;
use app\model\Applet;
use app\model\Link;
use app\model\EchoRuleConfig;
use app\model\CustomReply;
use app\model\KeywordReply;
use app\model\SubscribeRecord;
use app\model\Videos;
use think\facade\Cache;
use think\facade\Log;
use think\facade\Queue;
use WXBizMsgCrypt;

class WechatService
{
    private $mchId;
    private $serialNo;
    private $appid;

    private $userModel;
    private $officialAccountsModel;
    private $payRecordModel;
    private $appletModel;
    private $linkModel;
    private $echoRuleConfigModel;
    private $customReplyModel;
    private $keywordReplyModel;
    private $subscribeRecordModel;
    private $videoModel;
    private $refundRecordModel;

    //虚拟支付支付的类型
    const VIRTUAL_PAYMENT_MODE_SHORT_SERIES_GOODS = 'short_series_goods';//道具直购
    const VIRTUAL_PAYMENT_MODE_SHORT_SERIES_COIN = 'short_series_coin';//代币充值

    public function __construct(PayRecord $payRecordModel, User $userModel, OfficialAccounts $officialAccountsModel, Applet $appletModel, Link $linkModel, EchoRuleConfig $echoRuleConfigModel, CustomReply $customReplyModel, KeywordReply $keywordReplyModel, SubscribeRecord $subscribeRecordModel, Videos $videoModel, RefundRecord $refundRecordModel)
    {
        $this->payRecordModel = $payRecordModel;
        $this->userModel = $userModel;
        $this->officialAccountsModel = $officialAccountsModel;
        $this->appletModel = $appletModel;
        $this->linkModel = $linkModel;
        $this->echoRuleConfigModel = $echoRuleConfigModel;
        $this->customReplyModel = $customReplyModel;
        $this->keywordReplyModel = $keywordReplyModel;
        $this->subscribeRecordModel = $subscribeRecordModel;
        $this->videoModel = $videoModel;
        $this->refundRecordModel = $refundRecordModel;
    }
    /**
     * 从指定的数字数组中根据概率获取一个数字
     * 示例用法，传入指定的数字数组和获取数字7的概率为30%
     * $numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
     * $result = randomFromNumbersWithProbability($numbers, 7, 0.3);
     *
     * @param array $numbers 数字数组
     * @param int $targetNumber 目标数字
     * @param float $probability 获取目标数字的概率（0到1之间的小数）
     * @return int 获取的数字
     */
    public function randomFromNumbersWithProbability(array $numbers, int $targetNumber, float $probability = 0.5): int
    {
        // 确保概率在0到1之间
        $probability = max(0, min(1, $probability));

        // 判断是否获取目标数字
        if (rand(0, 100) / 100 <= $probability) {
            return $targetNumber;
        }

        // 从指定数组中随机选择一个数字
        $randomIndex = array_rand($numbers);
        return $numbers[$randomIndex];
    }

    /**
     * 从指定的数字数组中根据概率获取一个数字
     *   // 示例用法，传入数字数组和对应的概率
     *   $numbersWithProbabilities = [
     *      5 => 0.1,
     *      7 => 0.2,
     *      8 => 0.3,
     *      9 => 0.2,
     *      10 => 0.2,
     *   ];
     *
     *   for ($i = 0; $i < 10; $i++) {
     *      $result = $this->randomFromNumbersWithProbabilityArr($numbersWithProbabilities);
     *      echo $result . PHP_EOL;
     *   }
     *
     * @param array $numbersWithProbabilities 关联数组，键为数字，值为概率（0到1之间的小数）
     * @return int 获取的数字
     */
    public function randomFromNumbersWithProbabilityArr(array $numbersWithProbabilities): int
    {
        $numbers = array_keys($numbersWithProbabilities);
        $totalProbability = 0;

        // 累计计算所有数字的概率总和
        foreach ($numbersWithProbabilities as $probability) {
            $totalProbability += $probability;
        }

        // 确保总概率在0到1之间
        $totalProbability = max(0, min(1, $totalProbability));

        // 生成一个0到总概率之间的随机数
        $randomValue = mt_rand() / mt_getrandmax() * $totalProbability;

        $currentProbability = 0;

        // 根据概率获取对应的数字
        foreach ($numbersWithProbabilities as $number => $probability) {
            $currentProbability += $probability;
            if ($randomValue <= $currentProbability) {
                return $number;
            }
        }

        // 如果未能获取任何数字，返回从数组 $numbers 中随机选择的数字
        $randomIndex = array_rand($numbers);
        return $numbers[$randomIndex];
    }


    // 微信官方jsapi小程序支付
    public function wechatUnifyPay($params, $userId)
    {
        /*$rand = [7,8,9];
        $randKey = array_rand($rand);
        $randNum = $rand[$randKey];
        $wechatPayConfig = config('enum.wechat_pay_' . $randNum);*/

        $appId = $params['app_id'] ?? 0;
        $appletData = $this->appletModel->getInfoByWhere(['app_id' => $appId], 'id, app_id, company_entity_id, virtual_pay_switch, payment_rate');
        $company_entity_id = $appletData ? $appletData['company_entity_id'] : 0;
        $wechat_pay = config('enum.wechat_pay_config');
        switch ($company_entity_id) {
            case Applet::COMPANY_ENTITY_THREE:
                $wechatPayConfig = $wechat_pay[Applet::COMPANY_ENTITY_THREE] ?? [];
                break;
            case Applet::COMPANY_ENTITY_LIUYI:
            default:
                $wechatPayConfig = $wechat_pay[Applet::COMPANY_ENTITY_LIUYI] ?? [];
                break;
        }

        if (!$wechatPayConfig)  return ['message' => '配置错误'];

        $rand = array_keys($wechatPayConfig);
        $randKey = array_rand($rand);
        $randNum = $rand[$randKey];
        $wechatPayConfig = $wechatPayConfig[$randNum] ?? [];
        if (empty($wechatPayConfig)) return ['message' => '内部配置错误'];

        $name = isset($params['name']) ? $params['name'] : '测试数据';
        $name = addslashes(strip_tags($name));
        $total = isset($params['total_fee']) ? $params['total_fee'] : '1';
        $number = $params['number'];
        $type = isset($params['type']) ? $params['type'] : 1;

        $orderNo =Common::Snowflake();// date('YmdHis') . rand(1000, 10000);

        $userInfo = $this->userModel->getInfoById($userId, 'id, link_id, activation_time, official_account_id, interpolate_id, advertiser_id, applet_id, open_id, mobile_system');
        $appletData = $this->appletModel->getInfoById(['id' => $userInfo['applet_id']], 'app_id, name');

        if (!empty($userInfo['link_id']) && empty($userInfo['advertiser_id'])) {
            // 腾讯未匹配到的投放数据延时到订单匹配
            $linkData = $this->linkModel->getInfoByWhere(['id' => $userInfo['link_id'], 'media_id' => 4], 'id');
            if (!empty($linkData)) {
                $redis = Cache::store('redis')->handler();
                $redis->select(6);
                $monitoringData = $redis->get(md5($userInfo['open_id']));

                if (!empty($monitoringData)) {
                    $monitoringData = json_decode($monitoringData, true);
                    $userSaveData['clickid'] = $monitoringData['click_id'];
                    $userSaveData['advertiser_id'] = $monitoringData['advertiser_id'];
                    $userSaveData['back_url'] = $monitoringData['callback'];
                    $userSaveData['id'] = $userInfo['id'];
                    $this->userModel->edit($userSaveData);
                    $userInfo['advertiser_id'] = $monitoringData['advertiser_id'];
                }
            }
        }

        $saveData = [
            'uid'        => $userId,
            'order_no'   => $orderNo,
            'name'       => $name,
            'type'       => $type,
            'number'     => $number,
            'total_fee'  => round($total / 100, 2),
            'pay_merchant'  => $randNum,
            'created_at' => time(),
            'consume_date'    => date('Ymd'),
            'consume_month'   => date('Ym'),
            'link_id'         => !empty($userInfo['link_id']) ? $userInfo['link_id'] : null,
            'activation_time' => !empty($userInfo['activation_time']) ? $userInfo['activation_time'] : null,
            'activation_date' => !empty($userInfo['activation_time']) ? date('Ymd', strtotime($userInfo['activation_time'])) : null,
            'official_account_id' => !empty($userInfo['official_account_id']) ? $userInfo['official_account_id'] : null,
            'interpolate_id' => !empty($userInfo['interpolate_id']) ? $userInfo['interpolate_id'] : null,
            'advertiser_id' => !empty($userInfo['advertiser_id']) ? $userInfo['advertiser_id'] : null,
            'device_type' => WechatService::getDeviceType($userInfo['mobile_system'] ?? ''),
            'activity_id' => $params['activity_id'] ?? 0,
        ];

        if (!empty($params['video_id'])) {
            $saveData['video_id'] = $params['video_id'];
        }
        $payId = $this->payRecordModel->edit($saveData);

        $this->appid = $appid = $appletData['app_id'];
        $this->mchId = $mchid = $wechatPayConfig['mch_id'];
        $this->serialNo = $wechatPayConfig['serial_no'];
        $time = time();
        $nonceStr = Common::nonceStr(32);

        $openId = $userInfo['open_id'];

        $options = [
            'appid'        => $appid,
            'mchid'        => $mchid,
            'description'  => PayRecord::getPaymentDescription($type,$name),
            'out_trade_no' => $orderNo,
            'notify_url'   => 'https://' . config('app.app_host') . '/api/wx/callback',
            'amount' => [
                'total' => intval($total)
                // 'total' => 1
            ],
            'payer' => [
                'openid' => $openId
            ]
        ];

        // 处理过后的url地址 注:去掉前方的域名即可
        $disposeUrl = '/v3/pay/transactions/jsapi';
        // 获取请求主体的json数据
        $requestBody  = json_encode($options);
        // 获取参与签名的主体数据
        $signBody = self::signBody($disposeUrl, $time, $nonceStr, $requestBody);
        // 获取签名值
        $signature = self::getSignature($signBody, $randNum);

        $header = self::getHeader($nonceStr, $signature, $time);

        $url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi';
        $prepayIdData = Common::curlRequest($url, 'POST', $requestBody, $header);

        // 将返回的数据转化为数组
        $disposePrepayId = json_decode($prepayIdData, true);
        if (!isset($disposePrepayId['prepay_id'])) {
            return ['message' => '小程序和商户号不匹配'];
        }
        // 从转化的数组中获取prepay_id
        $prepayId = $disposePrepayId['prepay_id'];
        // 构造生成支付签名的签名主体数据
        $paySignBody = self::paySignBody($time, $nonceStr, $prepayId);
        // 对构造好的签名数据主体进行签名
        $paySign = self::getSignature($paySignBody, $randNum);

        $time = (string)$time; //将时间戳由int转化为string类型
        // 获取返回给小程序段的数据
        $data = self::requestPayData($nonceStr, $prepayId, $paySign, $time, $orderNo);
        return json_decode($data, true);
    }


    // 微信官方jsapi小程序支付
    public function wechatUnifyPayNew($params, $userId)
    {
        $appId = $params['app_id'] ?? 0;

        $appletData = $this->appletModel->getInfoByWhere(['app_id' => $appId], 'id, app_id, company_entity_id, virtual_pay_switch, payment_rate');
        //没有link_id的并且虚拟支付开关打开，直接走虚拟支付
        if (empty($params['link_id']) && $appletData['virtual_pay_switch'] == 1){
            return self::virtualPayment($params,$userId);
        }
        $randNum = $this->randomFromNumbersWithProbabilityArr(Applet::getPaymentRate($appletData['payment_rate']));
        //如果是iOS用户直接走米花第三方跳转
        $userInfoRedis = $this->getUserInfo($userId);
        $mobileSystem = $userInfoRedis['mobile_system'] ?? '';
        if (self::getPlatform($mobileSystem) == 'ios'){
//            $randNum = PayRecord::PAY_MERCHANT_MIHUAAPPLETJUMP_DALIANQUMAN;
            return self::wechatUnifyPay($params, $userId);
        }
        //米花小程序跳转
        if (in_array($randNum,array_keys(PayRecord::PAY_MERCHANT_MIHUA_JUMP_ARR))) {
            //如果小程序配置了公司主体
            if (!empty($appletData['company_entity_id'])){
                switch ($appletData['company_entity_id']){
                    //大连趣漫主体
                    case Applet::COMPANY_ENTITY_DALIANQUMAN:
                        $randNum = PayRecord::PAY_MERCHANT_MIHUAAPPLETJUMP_DALIANQUMAN;
                        break;
                    //默认走比翼鸟公司主体
                    default:
                        $randNum = PayRecord::PAY_MERCHANT_MIHUAAPPLETJUMP_LIUYI;
                        break;
                }
            }
            return self::wechatThirdPartyAppletPayment1($params, $userId, $randNum);
        }
        //掌中付第三方
        elseif ($randNum == PayRecord::PAY_MERCHANT_ZHANGZHONGYUN) {
            return self::wechatThirdPartyAppletPayment($params, $userId);
        }
        //扫码富第三方
        elseif ($randNum == PayRecord::PAY_MERCHANT_SAOMAFU){
            return self::wechatThirdPartyCashierPreOrder($params, $userId);
        }
        else {
            //小程序虚拟支付开关是否打开
            if ($appletData['virtual_pay_switch'] == 1){
                return self::virtualPayment($params,$userId, $randNum);
            }else{
                return self::wechatUnifyPay($params, $userId);
            }
        }
    }

    //扫码富收银台预下单
    public function wechatThirdPartyCashierPreOrder($params, $userId)
    {
        $name = isset($params['name']) ? $params['name'] : '测试数据';
        $name = addslashes(strip_tags($name));
        $total = isset($params['total_fee']) ? intval($params['total_fee']) : 1;
        $number = $params['number'];
        $type = isset($params['type']) ? $params['type'] : 1;
        $orderNo = date('YmdHis') . rand(1000, 10000);

        $userInfo = $this->userModel->getInfoById($userId, 'link_id, activation_time, official_account_id, interpolate_id, advertiser_id, applet_id, open_id, mobile_system');
        $appletData = $this->appletModel->getInfoById(['id' => $userInfo['applet_id']], 'app_id, name');
        if (empty($userInfo) || empty($appletData)) {
            return ['message' => '参数错误'];
        }

        $saveData = [
            'uid'        => $userId,
            'order_no'   => $orderNo,
            'name'       => $name,
            'type'       => $type,
            'number'     => $number,
            'total_fee'  => round($total / 100, 2),
            'pay_merchant' => PayRecord::PAY_MERCHANT_SAOMAFU,
            'created_at' => time(),
            'consume_date'    => date('Ymd'),
            'consume_month'   => date('Ym'),
            'link_id'         => !empty($userInfo['link_id']) ? $userInfo['link_id'] : null,
            'activation_time' => !empty($userInfo['activation_time']) ? $userInfo['activation_time'] : null,
            'activation_date' => !empty($userInfo['activation_time']) ? date('Ymd', strtotime($userInfo['activation_time'])) : null,
            'official_account_id' => !empty($userInfo['official_account_id']) ? $userInfo['official_account_id'] : null,
            'interpolate_id' => !empty($userInfo['interpolate_id']) ? $userInfo['interpolate_id'] : null,
            'advertiser_id' => !empty($userInfo['advertiser_id']) ? $userInfo['advertiser_id'] : null,
            'device_type' => WechatService::getDeviceType($userInfo['mobile_system'] ?? ''),
            'activity_id' => $params['activity_id'] ?? 0,
            'video_id' => $params['video_id'] ?? 0,
        ];

        if (!empty($params['video_id'])) {
            $saveData['video_id'] = $params['video_id'];
        }
        $payId = $this->payRecordModel->edit($saveData);

        // 调用第三方请求参数
        $payConfig = config('enum.wechat_third_party_applet_payment4');
        $sign_key = $payConfig['sign_key'] ?? '';
        $body_key = $payConfig['body_key'] ?? '';

        $configs = [
            'outTradeNo' => $orderNo,
            'mercCode' => $payConfig['mch_code'] ?? '',
            //'storeCode' => $payConfig['store_code'] ?? '',
            'orderAmount' => sprintf("%.2f", $total / 100),
            'clientType' => 'MINI',
            'tradeDesc' => '说明',
            'notifyUrl' => 'https://' . config('app.app_host') . '/api/wx/saoMaFuCallBack',
            'termIp' => Common::getClientIp(),
            'frontNotifyUrl' => '前端跳转地址',
            'openId' => $userInfo['open_id'],
            'appId' => $params['app_id'] ?? $appletData['app_id'],
        ];

        $saoMaFuService = new SaoMaFuService();
        $options = [
            'nonce' => time(),
            'platformMercCode' => $payConfig['platform_mch_code'] ?? '',
            'reqData' => stripslashes(json_encode($configs, JSON_UNESCAPED_UNICODE)),
        ];
        $options['sign'] = $saoMaFuService->ASCIISHA256($options, $sign_key);
        $options['reqData'] = $saoMaFuService->encryptStr(stripslashes(json_encode($configs, JSON_UNESCAPED_UNICODE)), $body_key);


        $url = $saoMaFuService->domain . '/api/trade/cashierPreOrder.html';
        $data = Common::curlRequest($url, 'POST', json_encode($options, JSON_UNESCAPED_UNICODE), [
            "Content-Type: application/json"
        ]);
        $result = json_decode($data, true);
        flog([
            'message' => 'saomafu_return1',
            'returnData' => $result,
        ],'sao_ma_fu_pay');

        if (!isset($result['respCode']) || $result['respCode'] != '0000') {
            return ['message' => $result['respMsg']];
        }
        $respData = $saoMaFuService->decryptStr($result['respData'], $body_key);
        $returnData = json_decode($respData, true);
        $returnData['pay_type'] = PayRecord::PAY_MERCHANT_SAOMAFU;
        flog([
            'message' => 'saomafu_return2',
            'returnData' => $returnData,
        ],'sao_ma_fu_pay');
        return $returnData;
    }

    //扫码富支付回调
    public function wechatThirdPartySMFCallback($param): array
    {
        $requestData = $param['reqData'] ?? '';
        if (!$requestData) return ['code' => '4001', 'message' => 'fail'];

        $payConfig = config('enum.wechat_third_party_applet_payment4');
        $body_key = $payConfig['body_key'] ?? '';

        $smf = new SaoMaFuService();
        $respData = $smf->decryptStr($requestData, $body_key);
        if (!$respData) return ['code' => '4001', 'message' => 'fail'];

        $data = json_decode($respData, true);
        flog([
            'message' => '扫码富第三方支付回调参数2',
            'params' => $data,
        ],'sao_ma_pay_back');

        if ($data['payStatus'] != 'SUCCESS') {
            return ['code' => '0000', 'message' => '成功'];
        }

        $thirdMercOrderNo = $data['thirdMercOrderNo'] ?? null;
        $result = $this->handleCallbackData($data['outTradeNo'], $thirdMercOrderNo, $data['orderNo']);

        return $result == 'success' ? ['code' => '0000', 'message' => '成功'] : ['code' => '4001', 'message' => 'fail'];
    }

    function handleCallbackData($orderId, $wechatOrderNo, $platformOrderNo): string
    {
        $orderNo = $orderId ?: 0;
        // 加锁
        $info = Db::name('pay_record')
            ->field('id, number, type, uid, link_id, total_fee, status, advertiser_id, activation_time, pay_merchant')
            ->where(['order_no' => $orderNo])
            ->lock(true)
            ->find();
        if (empty($info)) return 'FAIL';
        if (!empty($info['status'])) return 'success';

        Db::startTrans();
        try {
            // 修改支付状态
            $bool = Db::name('pay_record')
                ->where(['id' => $info['id']])
                ->update([
                    'wechat_order_no' => $wechatOrderNo,
                    'platform_order_no' => $platformOrderNo,
                    'status' => 1,
                    'pay_time' => time()
                ]);

            if (!$bool) {
                Db::rollback();
                return 'FAIL';
            }

            $userInfo = Db::name('user')->where(['id' => $info['uid']])->field('money, expiration_time, first_charge_date, clickid, interpolate_id, activation_time, ip, ua, applet_id, open_id, back_url')->lock(true)->find();

            // 是否支付过
            $isCharge = Db::name('pay_record')->field('id')->where(['uid' => $info['uid'], 'status' => 1])->find();

            if (in_array($info['type'],[Money::TYPE_COMMON,Money::TYPE_ACTIVITY])) {
                // 普通充值
                $saveData = [
                    'money' => $userInfo['money'] + $info['number']
                ];
            } elseif ($info['type'] == Money::TYPE_UNLOCK_ALL){
                $saveData = [
                    //'is_members'      => 0,
                    'updated_at' =>  time(),
                ];
            }else {
                // 会员
                $expirationTime = self::getMemberExpireTime($info['type'], $userInfo['expiration_time']);

                $saveData = [
                    'is_members'      => 1,
                    'expiration_time' => $expirationTime
                ];
            }

            if (!$isCharge) {
                $saveData['first_charge_date'] = time();
            }

            // 现有的余额加充值的K币或修改成会员
            $saveMoneyBool = Db::name('user')->where(['id' => $info['uid']])->update($saveData);

            if (!$saveMoneyBool) {
                Db::rollback();
                return 'FAIL';
            }

            if (!empty($userInfo['interpolate_id'])) {
                Db::name('keyword_reply')->where(['id' => $userInfo['interpolate_id']])->inc('order_amount', $info['total_fee'])->update();
            }

            if (!empty($info['link_id'])) {
                $linkData = Db::name('link')->where(['id' => $info['link_id']])->field('recharge_price, echo_rule_config_id')->find();
                $rechargePrice = $linkData['recharge_price'] + round($info['total_fee'], 2);
                Db::name('link')->where(['id' => $info['link_id']])->update([
                    'recharge_price' => $rechargePrice
                ]);
            }
            Db::commit();
        } catch (\Exception $e) {
            Db::rollback();
            return 'FAIL';
        }

        // 根据回传比例回传对应平台
        if ($info['link_id'] && $userInfo['clickid'] && $info['advertiser_id']) {
            $link = Db::name('link')->where(['id' => $info['link_id']])->field('is_new_back_setting')->find();

            $info['clickid'] = $userInfo['clickid'];
            $info['open_id'] = $userInfo['open_id'];
            $info['back_url'] = $userInfo['back_url'];
            $info['applet_id'] = $userInfo['applet_id'];

            if ($link['is_new_back_setting'] == 1) {
                $jobQueueName = 'transferBack';
                $isPushed = Queue::push('app\job\TransferBack', $info, $jobQueueName);
            } else {
                $jobQueueName = 'postBack';
                $isPushed = Queue::push('app\job\PostBack', $info, $jobQueueName);
            }
        }
        return 'success';
    }

    //扫码富退款回调
    public function wechatThirdPartySMFRefundCallBack($params)
    {
        $requestData = $params['reqData'] ?? '';
        if (!$requestData) return ['code' => '4001', 'message' => 'fail'];

        $payConfig = config('enum.wechat_third_party_applet_payment4');
        $body_key = $payConfig['body_key'] ?? '';

        $smf = new SaoMaFuService();
        $respData = $smf->decryptStr($requestData, $body_key);
        if (!$respData) return ['code' => '4001', 'message' => 'fail'];

        $data = json_decode($respData, true);
        flog([
            'message' => '扫码富订单退款返回结果2',
            'result' => $data,
        ],'order_refund_back');

        $refundNo = $data['outTradeNo'];
        if (empty($refundNo)) return ['code' => '4000', 'message' => 'fail'];
        $info = $this->refundRecordModel->where(['refund_no' => $refundNo])->find();
        if ($data['refundStatus'] != 'SUCCESS') {
            $saveData = [
                'status' => 2,
            ];
            if ($info){
                $info->save($saveData);
                $info->payRecord->save(['refund_status' => PayRecord::REFUND_STATUS_FAIL]);
            }
            return ['code' => '0000', 'message' => '成功'];
        }
        $saveData = [
            'refund_price' => $data['refundAmount'],
            'refund_time' => date('Y-m-d H:i:s'),
            'status' => 1,
            'updated_at' => date('Y-m-d H:i:s'),
        ];
        if ($info){
            $info->save($saveData);
            $info->payRecord->save(['refund_status' => PayRecord::REFUND_STATUS_SUCC]);
        }
        return ['code' => '0000', 'message' => '成功'];
    }

    // 微信掌中付第三方小程序支付
    public function wechatThirdPartyAppletPayment($params, $userId)
    {
        $name = isset($params['name']) ? $params['name'] : '测试数据';
        $name = addslashes(strip_tags($name));
        $total = isset($params['total_fee']) ? intval($params['total_fee']) : '1';
        $number = $params['number'];
        $type = isset($params['type']) ? $params['type'] : 1;

        $orderNo =Common::Snowflake();// date('YmdHis') . rand(1000, 10000);

        $userInfo = $this->userModel->getInfoById($userId, 'link_id, activation_time, official_account_id, interpolate_id, advertiser_id, applet_id, open_id, mobile_system');
        $isNewPay = $this->isTrue($params['is_new_pay'] ?? false);
        if ($isNewPay){
            $appId = $params['app_id'] ?? 0;
            $appletData = $this->appletModel->getInfoByWhere(['app_id' => $appId], 'app_id, name');
        }else{
            $appId = $userInfo['applet_id'] ?? 0;
            $appletData = $this->appletModel->getInfoById(['id' => $appId], 'app_id, name');
        }

        $saveData = [
            'uid'        => $userId,
            'order_no'   => $orderNo,
            'name'       => $name,
            'type'       => $type,
            'number'     => $number,
            'total_fee'  => round($total / 100, 2),
            'pay_merchant' => 5,
            'created_at' => time(),
            'consume_date'    => date('Ymd'),
            'consume_month'   => date('Ym'),
            'link_id'         => !empty($userInfo['link_id']) ? $userInfo['link_id'] : null,
            'activation_time' => !empty($userInfo['activation_time']) ? $userInfo['activation_time'] : null,
            'activation_date' => !empty($userInfo['activation_time']) ? date('Ymd', strtotime($userInfo['activation_time'])) : null,
            'official_account_id' => !empty($userInfo['official_account_id']) ? $userInfo['official_account_id'] : null,
            'interpolate_id' => !empty($userInfo['interpolate_id']) ? $userInfo['interpolate_id'] : null,
            'advertiser_id' => !empty($userInfo['advertiser_id']) ? $userInfo['advertiser_id'] : null,
            'device_type' => WechatService::getDeviceType($userInfo['mobile_system'] ?? ''),
            'activity_id' => $params['activity_id'] ?? 0,
        ];

        if (!empty($params['video_id'])) {
            $saveData['video_id'] = $params['video_id'];
        }
        $payId = $this->payRecordModel->edit($saveData);

        // 调用第三方请求参数
        $wechatPayConfig = config('enum.wechat_third_party_applet_payment');
        $mchId = $wechatPayConfig['mch_id'];
        $appid = $wechatPayConfig['appid'];
        $appkey = $wechatPayConfig['appkey'];
        $service = $wechatPayConfig['service'];

        $productName = $type == 1 ? '充值K币-' : '充值会员-' . $name;

        $options = [
            'mchId' => $mchId,
            'appid' => $appid,
            'version' => '3.0',
            'charset' => 'UTF-8',
            'signType' => 'MD5',
            'money' => $total,
            'outTradeNo' => $orderNo,
            'productName' => urlencode($productName),
            'productDesc' => $appletData['name'] . '-' . $productName,
            'subAppid' => $appletData['app_id'],
            'openid' => $userInfo['open_id'],
            'notifyUrl' => 'https://' . config('app.app_host') . '/api/wx/thirdPartyCallback',
            'ip' => Common::getClientIp(),
            'sign' => md5($appid . $service . $total . $orderNo . $appkey)
        ];

        $url = 'https://pay.palmpay.cn/sdkServer/thirdpays/pay/WECHAT_MNPROGRAM';
        $requestBody = http_build_query($options);
        $requestUrl = $url . '?' . $requestBody;
        $data = Common::curlRequest($requestUrl, 'GET');

        $result = json_decode($data, true);
        if ($result['errcode']) {
            return ['message' => $result['err']];
        }
        $returnData = json_decode($result['result']['pay_info'], true);
        $returnData['is_jump_applet'] = 2;
        flog([
            'message' => '掌中付支付返回给前端数据',
            'returnData' => $returnData,
        ],'zhang_zhong_fu_pay');
        return $returnData;
    }

    // 米花第三方支付小程序支付
    public function wechatThirdPartyAppletPayment1($params, $userId, $payMerchant = PayRecord::PAY_MERCHANT_MIHUAAPPLETJUMP_LIUYI)
    {
        $name = isset($params['name']) ? $params['name'] : '测试数据';
        $name = addslashes(strip_tags($name));
        $total = isset($params['total_fee']) ? intval($params['total_fee']) : '1';
        $number = $params['number'];
        $type = isset($params['type']) ? $params['type'] : 1;

        $orderNo = Common::Snowflake();//date('YmdHis') . rand(1000, 10000);

        $userInfo = $this->userModel->getInfoById($userId, 'link_id, activation_time, official_account_id, interpolate_id, advertiser_id, applet_id, open_id, mobile_system');
        $appletData = $this->appletModel->getInfoById(['id' => $userInfo['applet_id']], 'app_id, name');

        $saveData = [
            'uid'        => $userId,
            'order_no'   => $orderNo,
            'name'       => $name,
            'type'       => $type,
            'number'     => $number,
            'total_fee'  => round($total / 100, 2),
            'pay_merchant' => $payMerchant,
            'created_at' => time(),
            'consume_date'    => date('Ymd'),
            'consume_month'   => date('Ym'),
            'link_id'         => !empty($userInfo['link_id']) ? $userInfo['link_id'] : null,
            'activation_time' => !empty($userInfo['activation_time']) ? $userInfo['activation_time'] : null,
            'activation_date' => !empty($userInfo['activation_time']) ? date('Ymd', strtotime($userInfo['activation_time'])) : null,
            'official_account_id' => !empty($userInfo['official_account_id']) ? $userInfo['official_account_id'] : null,
            'interpolate_id' => !empty($userInfo['interpolate_id']) ? $userInfo['interpolate_id'] : null,
            'advertiser_id' => !empty($userInfo['advertiser_id']) ? $userInfo['advertiser_id'] : null,
            'device_type' => WechatService::getDeviceType($userInfo['mobile_system'] ?? ''),
            'activity_id' => $params['activity_id'] ?? 0,
        ];

        if (!empty($params['video_id'])) {
            $saveData['video_id'] = $params['video_id'];
        }
        $payId = $this->payRecordModel->edit($saveData);

        // 调用第三方请求参数
        $enumIndex = PayRecord::PAY_MERCHANT_MIHUA_ARR[$payMerchant] ?? PayRecord::PAY_MERCHANT_MIHUAAPPLETJUMP_LIUYI;
        $wechatPayConfig = config('enum.'.$enumIndex);

        $options = [
            'merAccount' => $wechatPayConfig['mch_code'],
            'merNo' => $wechatPayConfig['mch_id'],
            'orderId' => $orderNo,
            'time' => time(),
            'amount' => strval($total),
            'product' => $name,
            'payWay' => 'WEIXIN',
            'payType' => 'MINIAPP_WEIXIN',
            'userIp' => Common::getClientIp(),
            'notifyUrl' => 'https://' . config('app.app_host') . '/api/wx/thirdPartyCallback1',
            'openId' => $userInfo['open_id'],
            'appId' => $params['app_id'] ?? $appletData['app_id'],
        ];

        $url = 'https://platform.mhxxkj.com/paygateway/mbpay/order/v1';

        $sign = self::getSign($options, $wechatPayConfig['private_key']);

        $options['sign'] = $sign;
        $encodeData = self::encryptData($options, $wechatPayConfig['private_key']);

        $requestData = array(
            'merAccount' => $wechatPayConfig['mch_code'], // 商户标识
            'data' => $encodeData
        );

        $data = Common::curlPostRequest($url, http_build_query($requestData));

        $result = json_decode($data, true);

        if ($result['code'] != '000000') {
            return ['message' => $result['msg']];
        }
        $returnData = json_decode($result['data']['payInfo'], true);
        $returnData['is_jump_applet'] = in_array($payMerchant,[PayRecord::PAY_MERCHANT_MIHUAAPPLETJUMP_LIUYI,PayRecord::PAY_MERCHANT_MIHUAAPPLETJUMP_DALIANQUMAN]) ? 1 : 0;
        flog([
            'message' => '小程序米花支付返回给前端数据',
            'returnData' => $returnData,
        ],'mihua_pay');
        return $returnData;
    }

    // 加密
    function encrypt($data, $privateKey)
    {
        $makesureProvider = $this->makesureProvider($privateKey, 1);

        $encrypted = '';
        if ($makesureProvider['is_private']) {
            foreach (str_split($data, 117) as $chunk) {
                $r = openssl_private_encrypt($chunk, $encryptData, $makesureProvider['key_provider'], OPENSSL_PKCS1_PADDING);
                $encrypted .= $encryptData;
            }
        } else {
            foreach (str_split($data, 117) as $chunk) {
                $r = openssl_public_encrypt($chunk, $encryptData, $makesureProvider['key_provider'], OPENSSL_PKCS1_PADDING);
                $encrypted .= $encryptData;
            }
        }

        return $r ? $data = base64_encode($encrypted) : null;
    }

    // 获取加密串
    function encryptData($params, $privateKey)
    {
        ksort($params);
        $data = self::encrypt(json_encode($params), $privateKey);
        return $data;
    }

    // 
    function makesureProvider($privateKey, $keyFormat)
    {
        $isPrivate = strlen($privateKey) > 500;

        switch ($keyFormat) {
            case 1: {

                    $privateKey = chunk_split($privateKey, 64, "\r\n"); //转换为pem格式的公钥

                    if ($isPrivate) {
                        $privateKey = "-----BEGIN PRIVATE KEY-----\r\n" . $privateKey . "-----END PRIVATE KEY-----";
                    } else {
                        $privateKey = "-----BEGIN PUBLIC KEY-----\r\n" . $privateKey . "-----END PUBLIC KEY-----";
                    }

                    break;
                }
        }

        $keyProvider = $isPrivate ? openssl_pkey_get_private($privateKey) : openssl_pkey_get_public($privateKey);
        return ['key_provider' => $keyProvider, 'is_private' => $isPrivate];
    }

    // 米花回调解密
    public function decrypt($data, $publicKey)
    {
        $makesureProvider = $this->makesureProvider($publicKey, 1);
        $data = base64_decode($data);
        $crypto = '';
        foreach (str_split($data, 128) as $chunk) {
            if ($makesureProvider['is_private']) {
                $r = openssl_private_decrypt($chunk, $decrypted, $makesureProvider['key_provider'], OPENSSL_PKCS1_PADDING);
            } else {
                $r = openssl_public_decrypt($chunk, $decrypted, $makesureProvider['key_provider'], OPENSSL_PKCS1_PADDING);
            }
            $crypto .= $decrypted;
        }
        return $crypto;
    }

    /**
     * PHP7 DES 解密
     * @param string $str 需要解密的字符串
     * @param string $key 密钥
     * @param string $type 解密类型，支持base64_decode、hex2bin等
     * @return bool|string
     */
    public static function sslDecrypt($str, $key, $type = 'base64_decode')
    {
        $decrypt = call_user_func($type, $str);
        return openssl_decrypt($decrypt, 'DES-ECB', $key, OPENSSL_RAW_DATA);
    }

    // 获取米花第三方请求签名
    public function getSign($requestBody, $keySercet)
    {
        ksort($requestBody);
        $data = '';
        foreach ($requestBody as $key => $value) {
            $data .= trim($value);
        }
        $sign = strtoupper(md5($data . $keySercet));
        return $sign;
    }

    /**
     * @param $nonce_str :32位随机字符串,与上方的随机字符串保持一致
     * @param $signature :上方生成的签名值
     * @return string[]
     */
    public function getHeader($nonceStr, $signature, $time)
    {
        $token = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%s",signature="%s"', $this->mchId, $this->serialNo, $nonceStr, $time, $signature);
        $data = array(
            'Accept: application/json',
            'Content-Type: application/json',
            'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.71',
            "Authorization: WECHATPAY2-SHA256-RSA2048 $token",
        );
        return $data;
    }

    /**
     * 用于生成签名数据主体
     * @param $disposeUrl :此处的url是接口地址去除域名后的url
     * @param $nonce_str :32位随机字符串
     * @param $requestBody :请求报文的主体
     * @return string
     */
    public function signBody($disposeUrl, $time, $nonce_str, $requestBody)
    {
        $data = array(
            'POST', $disposeUrl, $time, $nonce_str, $requestBody, ''
        );
        return join("\n", $data);
    }

    /**
     * 用于生成签名值
     * @param $data :用于签名的数据
     * @return string
     */
    public function getSignature($data, $randNum)
    {
        $private = file_get_contents('./cert/wechat_' . $randNum . '/apiclient_key.pem');
        $key = openssl_pkey_get_private($private);
        openssl_sign($data, $signature, $key, 'sha256WithRSAEncryption');
        return base64_encode($signature);
    }

    /**
     * 构造对prepay_id再次签名的签名数据主体
     * @param $time :时间戳
     * @param $nonceStr :32位随机字符串
     * @param $prepayId :预支付参数
     * @return string
     */
    public function paySignBody($time, $nonceStr, $prepayId)
    {
        $data = array(
            $this->appid, $time, $nonceStr, "prepay_id=$prepayId", ''
        );
        return join("\n", $data);
    }

    /**
     * 构造返回给小程序段的数据,json数据
     * @param $nonce_str :32位随机字符串
     * @param $prepay_id :预支付参数
     * @param $paySign :签名值
     * @param $time :时间戳
     * @param $outTradeNo :商户订单号
     * @return false|string
     */
    public function requestPayData($nonceStr, $prepayId, $paySign, $time, $outTradeNo)
    {
        $data = array(
            'nonceStr' => $nonceStr,
            'package' => 'prepay_id=' . $prepayId,
            'paySign' => $paySign,
            'timeStamp' => $time,
            'signType' => 'RSA',
            'out_trade_no' => $outTradeNo
        );
        return json_encode($data);
    }

    /**
     * 微信官方支付回调
     */
    public function wechatPayCallBack($params)
    {
        // 记录日志
        $log = fopen('./pay_callback.txt', 'a+');

        if ($log){
            fwrite($log, date('Y-m-d H:i:s').'：'.$params.PHP_EOL);
        }

        fclose($log);

        $responseData = json_decode($params, true);
        $key = config("enum.wechat_pay_1.apiv3_mch_key");
        $text = base64_decode($responseData['resource']['ciphertext']);

        if (!$text) {
            return 0;
        }
        // 需打开sodium扩展
        $jsonData = sodium_crypto_aead_aes256gcm_decrypt($text, $responseData['resource']['associated_data'], $responseData['resource']['nonce'], $key);
        $data = json_decode($jsonData, true);

        if ($data['trade_state'] != 'SUCCESS') {
            return 0;
        }

        $orderNo = $data['out_trade_no'];
        Db::startTrans();
        try {
            // 加锁
            $info = Db::name('pay_record')->field('id, number, type, uid, link_id, total_fee, status, advertiser_id, activation_time')->where(['order_no' => $orderNo])->lock(true)->find();

            if (empty($info) || !empty($info['status'])) {
                Db::rollback();
                return 0;
            }

            // 是否支付过
            $isCharge = Db::name('pay_record')->field('id')->where(['uid' => $info['uid'], 'status' => 1])->find();

            // 修改支付状态
            $bool = PayRecord::update([
                'wechat_order_no' => $data['transaction_id'],
                'status' => 1,
                'pay_time' => time()
            ],['id' => $info['id']]);

            if (!$bool) {
                Db::rollback();
                return 0;
            }

            $userInfo = Db::name('user')->where(['id' => $info['uid']])->field('money, expiration_time, first_charge_date, clickid, interpolate_id, activation_time, ip, ua, applet_id, open_id, back_url')->lock(true)->find();

            if (in_array($info['type'],[Money::TYPE_COMMON,Money::TYPE_ACTIVITY])) {
                // 普通充值
                $saveData = [
                    'money' => $userInfo['money'] + $info['number']
                ];
            }elseif ($info['type'] == Money::TYPE_UNLOCK_ALL){
                $saveData = [
                    //'is_members'      => 0,
                    'updated_at' =>  time(),
                ];
            } else {
                // 会员
                $expirationTime = self::getMemberExpireTime($info['type'], $userInfo['expiration_time']);

                $saveData = [
                    'is_members'      => 1,
                    'expiration_time' => $expirationTime
                ];
            }

            if (!$isCharge) {
                $saveData['first_charge_date'] = time();
            }

            // 现有的余额加充值的K币或修改成会员
            $saveMoneyBool = Db::name('user')->where(['id' => $info['uid']])->update($saveData);

            if (!$saveMoneyBool) {
                Db::rollback();
                return 0;
            }

            if (!empty($userInfo['interpolate_id'])) {
                Db::name('keyword_reply')->where(['id' => $userInfo['interpolate_id']])->inc('order_amount', $info['total_fee'])->update();
            }
            if (!empty($info['link_id'])) {
                $linkData = Db::name('link')->where(['id' => $info['link_id']])->field('recharge_price, echo_rule_config_id')->find();
                $rechargePrice = $linkData['recharge_price'] + round($info['total_fee'], 2);
                Db::name('link')->where(['id' => $info['link_id']])->update([
                    'recharge_price' => $rechargePrice
                ]);
            }

            Db::commit();
        } catch (\Exception $e) {
            Db::rollback();
            flog(['error' => $e->getMessage().'_'.$e->getLine(), 'out_trade_no' => $orderNo], 'wechat_pay');
            return 0;
        }

        // 根据回传比例回传对应平台
        if ($info['link_id'] && $userInfo['clickid'] && $info['advertiser_id']) {
            $link = Db::name('link')->where(['id' => $info['link_id']])->field('is_new_back_setting')->find();

            $info['clickid'] = $userInfo['clickid'];
            $info['open_id'] = $userInfo['open_id'];
            $info['back_url'] = $userInfo['back_url'];
            $info['applet_id'] = $userInfo['applet_id'];

            if ($link['is_new_back_setting'] == 1) {
                $jobQueueName = 'transferBack';
                $isPushed = Queue::push('app\job\TransferBack', $info, $jobQueueName);
            } else {
                $jobQueueName = 'postBack';
                $isPushed = Queue::push('app\job\PostBack', $info, $jobQueueName);
            }
        }

        return 1;
    }

    /**
     * 掌中付微信支付第三方支付回调
     */
    public function wechatThirdPartyPayCallBack($params)
    {
        // 记录日志
        $log = fopen('./third_pay_callback.txt', 'a+');

        if ($log){
            fwrite($log, date('Y-m-d H:i:s').'：'.json_encode($params).PHP_EOL);
        }

        fclose($log);

        if (!empty($params['status']) && $params['status'] != 'success') {
            return 'fail';
        }

        $orderNo = $params['outTradeNo'];
        Db::startTrans();
        // 加锁
        $info = Db::name('pay_record')->field('id, number, type, uid, link_id, total_fee, status, advertiser_id, activation_time')->where(['order_no' => $orderNo])->lock(true)->find();

        if (empty($info) || !empty($info['status'])) {
            return 'fail';
        }

        // 修改支付状态
        $bool = Db::name('pay_record')->where(['id' => $info['id']])->update([
            'wechat_order_no' => $params['chorderid'],
            'platform_order_no' => $params['pdorderid'],
            'status' => 1,
            'pay_time' => time()
        ]);

        if (!$bool) {
            Db::rollback();
            return 'fail';
        }

        $userInfo = Db::name('user')->where(['id' => $info['uid']])->field('money, expiration_time, first_charge_date, clickid, interpolate_id, activation_time, ip, ua, applet_id, open_id, back_url')->lock(true)->find();

        // 是否支付过
        $isCharge = Db::name('pay_record')->field('id')->where(['uid' => $info['uid'], 'status' => 1])->find();

        if (in_array($info['type'],[Money::TYPE_COMMON,Money::TYPE_ACTIVITY])) {
            // 普通充值
            $saveData = [
                'money' => $userInfo['money'] + $info['number']
            ];
        } elseif ($info['type'] == Money::TYPE_UNLOCK_ALL){
            $saveData = [
                //'is_members'      => 0,
                'updated_at' =>  time(),
            ];
        }else {
            // 会员
            $expirationTime = self::getMemberExpireTime($info['type'], $userInfo['expiration_time']);

            $saveData = [
                'is_members'      => 1,
                'expiration_time' => $expirationTime
            ];
        }

        if (!$isCharge) {
            $saveData['first_charge_date'] = time();
        }

        // 现有的余额加充值的K币或修改成会员
        $saveMoneyBool = Db::name('user')->where(['id' => $info['uid']])->update($saveData);

        if (!$saveMoneyBool) {
            Db::rollback();
            return 'fail';
        }

        if (!empty($userInfo['interpolate_id'])) {
            Db::name('keyword_reply')->where(['id' => $userInfo['interpolate_id']])->inc('order_amount', $info['total_fee'])->update();
        }

        if (!empty($info['link_id'])) {
            $linkData = Db::name('link')->where(['id' => $info['link_id']])->field('recharge_price, echo_rule_config_id')->find();
            $rechargePrice = $linkData['recharge_price'] + round($info['total_fee'], 2);
            Db::name('link')->where(['id' => $info['link_id']])->update([
                'recharge_price' => $rechargePrice
            ]);
        }

        Db::commit();

        // 根据回传比例回传对应平台
        if ($info['link_id'] && $userInfo['clickid'] && $info['advertiser_id']) {

            $link = Db::name('link')->where(['id' => $info['link_id']])->field('is_new_back_setting')->find();

            $info['clickid'] = $userInfo['clickid'];
            $info['open_id'] = $userInfo['open_id'];
            $info['back_url'] = $userInfo['back_url'];
            $info['applet_id'] = $userInfo['applet_id'];

            if ($link['is_new_back_setting'] == 1) {
                $jobQueueName = 'transferBack';
                $isPushed = Queue::push('app\job\TransferBack', $info, $jobQueueName);
            } else {
                $jobQueueName = 'postBack';
                $isPushed = Queue::push('app\job\PostBack', $info, $jobQueueName);
            }
        }

        return 'success';
    }

    // 米花第三方支付回调
    public function wechatThirdPartyPayCallBack1($params)
    {
        // 记录日志
        flog([
            'message' => '米花第三方支付回调参数',
            'params' => $params,
        ],'wechat_pay');

        Db::startTrans();
        $orderNo = $params['orderId'] ?? 0;
        // 加锁
        $info = Db::name('pay_record')->field('id, number, type, uid, link_id, total_fee, status, advertiser_id, activation_time, pay_merchant')->where(['order_no' => $orderNo])->lock(true)->find();

        if (empty($info) || !empty($info['status'])) {
            return 'FAIL';
        }


        $enumIndex = PayRecord::PAY_MERCHANT_MIHUA_ARR[$info['pay_merchant']] ?? PayRecord::PAY_MERCHANT_MIHUAAPPLETJUMP_LIUYI;
        $wechatPayConfig = config('enum.'.$enumIndex);
        $data = self::decrypt($params['data'], $wechatPayConfig['public_key']);
        flog([
            'message' => '米花第三方支付回调数据解密',
            'data' => $data,
        ],'wechat_pay');

        $payCallBackData = json_decode($data, true);

        if ($payCallBackData['orderStatus'] != 'SUCCESS') {
            return 'FAIL';
        }

        // 修改支付状态
        $bool = Db::name('pay_record')->where(['id' => $info['id']])->update([
            'wechat_order_no' => $payCallBackData['bankTrxNo'],
            'platform_order_no' => $payCallBackData['mbOrderId'],
            'status' => 1,
            'pay_time' => time()
        ]);

        if (!$bool) {
            Db::rollback();
            return 'FAIL';
        }

        $userInfo = Db::name('user')->where(['id' => $info['uid']])->field('money, expiration_time, first_charge_date, clickid, interpolate_id, activation_time, ip, ua, applet_id, open_id, back_url')->lock(true)->find();

        // 是否支付过
        $isCharge = Db::name('pay_record')->field('id')->where(['uid' => $info['uid'], 'status' => 1])->find();

        if (in_array($info['type'],[Money::TYPE_COMMON,Money::TYPE_ACTIVITY])) {
            // 普通充值
            $saveData = [
                'money' => $userInfo['money'] + $info['number']
            ];
        } elseif ($info['type'] == Money::TYPE_UNLOCK_ALL){
            $saveData = [
                //'is_members'      => 0,
                'updated_at' =>  time(),
            ];
        }else {
            // 会员
            $expirationTime = self::getMemberExpireTime($info['type'], $userInfo['expiration_time']);

            $saveData = [
                'is_members'      => 1,
                'expiration_time' => $expirationTime
            ];
        }

        if (!$isCharge) {
            $saveData['first_charge_date'] = time();
        }

        // 现有的余额加充值的K币或修改成会员
        $saveMoneyBool = Db::name('user')->where(['id' => $info['uid']])->update($saveData);

        if (!$saveMoneyBool) {
            Db::rollback();
            return 'FAIL';
        }

        if (!empty($userInfo['interpolate_id'])) {
            Db::name('keyword_reply')->where(['id' => $userInfo['interpolate_id']])->inc('order_amount', $info['total_fee'])->update();
        }

        if (!empty($info['link_id'])) {
            $linkData = Db::name('link')->where(['id' => $info['link_id']])->field('recharge_price, echo_rule_config_id')->find();
            $rechargePrice = $linkData['recharge_price'] + round($info['total_fee'], 2);
            Db::name('link')->where(['id' => $info['link_id']])->update([
                'recharge_price' => $rechargePrice
            ]);
        }

        Db::commit();

        // 根据回传比例回传对应平台
        if ($info['link_id'] && $userInfo['clickid'] && $info['advertiser_id']) {

            $link = Db::name('link')->where(['id' => $info['link_id']])->field('is_new_back_setting')->find();

            $info['clickid'] = $userInfo['clickid'];
            $info['open_id'] = $userInfo['open_id'];
            $info['back_url'] = $userInfo['back_url'];
            $info['applet_id'] = $userInfo['applet_id'];

            if ($link['is_new_back_setting'] == 1) {
                $jobQueueName = 'transferBack';
                $isPushed = Queue::push('app\job\TransferBack', $info, $jobQueueName);
            } else {
                $jobQueueName = 'postBack';
                $isPushed = Queue::push('app\job\PostBack', $info, $jobQueueName);
            }
        }

        return 'SUCCESS';
    }

    //米花订单退款回调
    public function mhOrderRefundCallback($params)
    {
        // 记录日志
        flog([
            'message' => '米花订单退款回调开始',
            'params' => $params,
        ],'wechat_pay');
        $refundNo = $params['merchantRefundNo'] ?? '';

        $info = $this->refundRecordModel->where(['refund_no' => $refundNo])->find();

        if (empty($info)) {
            return 'FAIL';
        }

        $enumIndex = PayRecord::PAY_MERCHANT_MIHUA_ARR[$info['pay_merchant']] ?? PayRecord::PAY_MERCHANT_MIHUAAPPLETJUMP_LIUYI;
        $wechatPayConfig = config('enum.'.$enumIndex);
        $data = self::decrypt($params['data'], $wechatPayConfig['public_key']);

        $callBackData = json_decode($data, true);

        // 退款失败
        if ($callBackData['refundStatus'] != 'REFUND_SUCCESS') {
            $saveData = [
                'status' => 2,
            ];
            if ($info){
                $info->save($saveData);
                $info->payRecord->save(['refund_status' => PayRecord::REFUND_STATUS_FAIL]);
            }
            return 'FAIL';
        }
        $saveData = [
            'refund_price' => $callBackData['refundAmt'],
            'refund_time' => date('Y-m-d H:i:s',strtotime($callBackData['refundTime'])),
            'status' => 1,
            'updated_at' => date('Y-m-d H:i:s'),
        ];
        if ($info){
            $info->save($saveData);
            $info->payRecord->save(['refund_status' => PayRecord::REFUND_STATUS_SUCC]);
        }
        return 'SUCCESS';
    }

    // 获取会员到期时间
    public function getMemberExpireTime($type, $timestamp = 0)
    {
        if ($timestamp && $timestamp > time()) {
            // 大于当前时间表示续费
            switch ($type) {
                case 2: // 年卡会员
                    $expirationTime = strtotime('+1day', strtotime('+12' . ' month', $timestamp));
                    break;
                case 3: // 半年会员
                    $expirationTime = $timestamp + 86400 * 180;
                    break;
                case 4: // 季卡会员
                    $expirationTime = $timestamp + 86400 * 90;
                    break;
                case 5: // 60天会员
                    $expirationTime = $timestamp + 86400 * 60;
                    break;
                case 6: // 30天会员
                    $expirationTime = $timestamp + 86400 * 30;
                    break;
                case 7: // 10天会员
                    $expirationTime = $timestamp + 86400 * 10;
                    break;
                case 8: // 7天会员
                    $expirationTime = $timestamp + 86400 * 7;
                    break;
                case 11: // 3天会员
                    $expirationTime = $timestamp + 86400 * 3;
                    break;
                case 15: // 1天会员
                    $expirationTime = $timestamp + 86400 * 1;
                    break;
                default:
                    $expirationTime = time();
                    break;
            }
        } else {
            // 开通会员或会员已过期
            switch ($type) {
                case 2: // 年卡会员
                    $expirationTime = strtotime(date('Y-m-d', strtotime('+1day', strtotime(date('Y-m-d', strtotime('+12' . ' month'))))));
                    break;
                case 3: // 半年会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 181;
                    break;
                case 4: // 季卡会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 91;
                    break;
                case 5: // 60天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 61;
                    break;
                case 6: // 30天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 31;
                    break;
                case 7: // 10天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 11;
                    break;
                case 8: // 7天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 8;
                    break;
                case 11: // 3天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 4;
                    break;
                case 15: // 1天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 2;
                    break;
                default:
                    $expirationTime = time();
                    break;
            }
        }
        return $expirationTime;
    }

    // 微信公众号关注/取关
    public function wechatOfficialAccountLogin($postObj)
    {
        $openId = (string)$postObj->FromUserName;
        $eventKey = (string)$postObj->EventKey;

        $toUsername = (string)$postObj->ToUserName;
        $time = time();
        $msgtype = 'text';

        $textTpl = '<xml>
            <ToUserName><![CDATA[%s]]></ToUserName>
            <FromUserName><![CDATA[%s]]></FromUserName>
            <CreateTime>%s</CreateTime>
            <MsgType><![CDATA[%s]]></MsgType>
            <Content><![CDATA[%s]]></Content>
            </xml>';

        $data = $this->officialAccountsModel->getInfoByWhere(['original_id' => $toUsername], 'id, name, content, original_id, token, app_id, app_secret, encoding_aes_key, applet_id, follow_reply_switch');

        if (empty($data)) {
            $content = '欢迎关注！';
            return sprintf($textTpl, $openId, $toUsername, $time, $msgtype, $content);
        }

        // 查询用户
        if ($eventKey) {
            if (strpos($eventKey, '_')) {
                list($qrscene, $uid) = explode('_', $eventKey);
            } else {
                $uid = $eventKey;
            }

            $userInfo = $this->userModel->getDataByWhere(['id' => $uid], 'id, status');
        } else {
            $where = [
                'official_account_open_id' => $openId,
                'applet_id' => $data['applet_id']
            ];
            $userInfo = $this->userModel->getDataByWhere($where, 'id, status');
            if (!empty($userInfo)) {
                $uid = $userInfo['id'];
            }
        }

        // 获取缓存的用户信息
        $redis = Cache::store('redis')->handler();
        $key = config('redis_key.api.user_login');
        if (!empty($uid)) {
            $redisUserData = $redis->get($key . $uid);
        } else {
            $redisUserData = '';
        }

        $appletData = $this->appletModel->getInfoById($data['applet_id'], 'app_id');

        if (strtolower($postObj->Event) == 'subscribe') {
            // 关注
            if (!$userInfo) {
                // 注册用户
                $saveData = [
                    'official_account_open_id' => $openId,
                    'official_account_id' => $data['id'],
                    'applet_id' => $data['applet_id'],
                    'status' => 1,
                    'official_account_created_at' => time(),
                    'register_date' => date('Ymd'),
                    'last_login_time' => time()
                ];
                // file_put_contents('./login.txt', json_encode($saveData));
            } else {
                // 修改关注时间和关注状态
                $saveData = [
                    'id' => $userInfo['id'],
                    'official_account_open_id' => $openId,
                    'official_account_id' => $data['id'],
                    'applet_id' => $data['applet_id'],
                    'status' => 1,
                    'last_login_time' => time()
                ];

                // 修改redis用户数据的关注状态
                if ($redisUserData) {
                    $redisUserData = json_decode($redisUserData, true);
                    $redisUserData['status'] = 1;
                    $expire = config('redis_key.api.user_login_expire');
                    $redis->set($key . $uid, json_encode($redisUserData), $expire);
                }
            }

            $uid = $this->userModel->edit($saveData);

            // 判断是否开启了关注回复
            $switch = config('enum.follow_reply_switch');
            if ($data['follow_reply_switch'] == 1) {
                // 开启自动回复

                // 是否使用自定义回复
                $customReplyData = $this->customReplyModel->getInfoByWhere(['official_account_id' => $data['id'], 'type' => 1], 'up_text, down_text, title, link_url');

                if ($customReplyData) {
                    // 设置了自定义使用自定义
                    $content = $customReplyData['up_text'] . '

<a data-miniprogram-appid="' . $appletData['app_id'] . '" data-miniprogram-path="' . $customReplyData['link_url'] . '?official_account_id=' . $data['id'] . '&uid=' . $uid . '">' . $customReplyData['title'] . '</a>

' . $customReplyData['down_text'];
                } else {
                    // 否则使用默认
                    $content = '欢迎关注 「' . $data['name'] . '」

<a data-miniprogram-appid="' . $appletData['app_id'] . '" data-miniprogram-path="' . config('enum.applet_home_jump_url') . '?official_account_id=' . $data['id'] . '&uid=' . $uid . '">点击观看短剧</a>';
                }
            } else {
                // 关闭自动回复
                $content = '';
            }

            return sprintf($textTpl, $openId, $toUsername, $time, $msgtype, $content);
        } elseif (strtolower($postObj->Event) == 'unsubscribe') {
            // 取关
            if (!empty($userInfo)) {
                $saveData = [
                    'id' => $userInfo['id'],
                    'status' => 0,
                    'closing_time' => time()
                ];
                $this->userModel->edit($saveData);
            }

            // 修改redis用户数据的关注状态
            if ($redisUserData) {
                $redisUserData = json_decode($redisUserData, true);
                $redisUserData['status'] = 0;
                $expire = config('redis_key.api.user_login_expire');
                $redis->set($key . $uid, json_encode($redisUserData), $expire);
            }

            $content = '欢迎再来！';

            return sprintf($textTpl, $openId, $toUsername, $time, $msgtype, $content);
        } elseif (strtolower($postObj->Event) == 'scan') {
            // 扫码进入事件
            $content = '欢迎扫码进入 「' . $data['name'] . '」

<a data-miniprogram-appid="' . $appletData['app_id'] . '" data-miniprogram-path="' . config('enum.applet_home_jump_url') . '?official_account_id=' . $data['id'] . '&uid=' . $uid . '">点击观看短剧</a>';

            // 修改redis用户数据的关注状态
            if ($redisUserData) {
                $redisUserData = json_decode($redisUserData, true);
                $redisUserData['status'] = 1;
                $expire = config('redis_key.api.user_login_expire');
                $redis->set($key . $uid, json_encode($redisUserData), $expire);
            }

            return sprintf($textTpl, $openId, $toUsername, $time, $msgtype, $content);
        }
    }

    // 微信公众号自定义关键字回复
    public function wechatOfficialAccountCustomReply($postObj)
    {
        $openId = (string)$postObj->FromUserName;
        $createTime = (string)$postObj->CreateTime;
        $toUsername = (string)$postObj->ToUserName;
        $time = time();
        $msgtype = 'text';

        $textTpl = '<xml>
            <ToUserName><![CDATA[%s]]></ToUserName>
            <FromUserName><![CDATA[%s]]></FromUserName>
            <CreateTime>%s</CreateTime>
            <MsgType><![CDATA[%s]]></MsgType>
            <Content><![CDATA[%s]]></Content>
            </xml>';

        $data = $this->officialAccountsModel->getInfoByWhere(['original_id' => $toUsername], 'id, name, content, original_id, token, app_id, app_secret, encoding_aes_key, applet_id, sign_reply_switch');

        if (empty($data)) {
            $content = '欢迎关注！';
            return sprintf($textTpl, $openId, $toUsername, $time, $msgtype, $content);
        }

        // 查询用户
        $where = [
            'official_account_open_id' => $openId,
            'applet_id' => $data['applet_id']
        ];
        $userInfo = $this->userModel->getDataByWhere($where, 'id, status, interpolate_id, money');

        $uid = $userInfo['id'];
        $appletData = $this->appletModel->getInfoById($data['applet_id'], 'app_id');

        if (strstr($postObj->Content, '签到')) {
            if ($data['sign_reply_switch'] == 1) {
                $customReplyData = $this->customReplyModel->getInfoByWhere(['official_account_id' => $data['id'], 'type' => 2], 'up_text, down_text, title, link_url');

                if ($customReplyData) {
                    // 设置了自定义使用自定义
                    $content = $customReplyData['up_text'] . '

<a data-miniprogram-appid="' . $appletData['app_id'] . '" data-miniprogram-path="' . $customReplyData['link_url'] . '?official_account_id=' . $data['id'] . '&uid=' . $uid . '">' . $customReplyData['title'] . '</a>

' . $customReplyData['down_text'];
                } else {
                    // 否则使用默认
                    $content = '<a data-miniprogram-appid="' . $appletData['app_id'] . '" data-miniprogram-path="' . config('enum.sign_link_jump_url') . '?official_account_id=' . $data['id'] . '&uid=' . $uid . '">点击进入签到页面</a>';
                }
            } else {
                // 关闭自动回复
                $content = '';
            }
        } elseif ($postObj->Content == 8) {
            // 回复“8”每日领取K币
            $redis = Cache::store('redis')->handler();
            $redisKey = config('redis_key.today_k_coin_collection_record_key') . $uid;
            $redisValue = $redis->get($redisKey);

            if ($redisValue) {
                $content = '今日已领取过福利，请<a data-miniprogram-appid="' . $appletData['app_id'] . '" data-miniprogram-path="' . config('enum.sign_link_jump_url') . '?official_account_id=' . $data['id'] . '&uid=' . $uid . '">跳转小程序</a>查看';
            } else {
                // 领取 15 K币
                $bool = $this->userModel->edit(['id' => $uid, 'money' => intval($userInfo['money']) + 15]);
                if ($bool) {
                    // 领取记录存储到redis
                    $endTime = strtotime(date('Y-m-d 23:59:59'));
                    $redis->set($redisKey, (string)time(), $endTime - time());
                    $content = '今日福利已领取成功，获得15K币';
                } else {
                    $content = '今日福利领取失败~';
                }
            }
        } else {
            $keyword = $postObj->Content;

            $keywordReplyData = $this->keywordReplyModel->getInfoByWhere(['keyword' => $keyword, 'official_account_id' => $data['id']], 'id, type, link_type, video_id, title, cover, link_url, up_text, down_text, media_id');

            if (!$keywordReplyData) {
                // 未匹配到该关键字
                $keywordReply = $this->keywordReplyModel->getDataByWhere(['official_account_id' => $data['id']], 'keyword');

                if ($keywordReply) {
                    $keywords = array_column($keywordReply, 'keyword');

                    $content = '未匹配到该关键字，您可以试试输入以下关键字

' . join(',', $keywords);
                } else {
                    $content = '未匹配到该关键字';
                }

                return sprintf($textTpl, $openId, $toUsername, $time, $msgtype, $content);
            }

            // 修改用户的内推id
            $this->userModel->where(['id' => $uid])->update(['interpolate_id' => $keywordReplyData['id']]);

            if ($keywordReplyData['type'] == 1) {
                $content = $keywordReplyData['up_text'] . '

<a data-miniprogram-appid="' . $appletData['app_id'] . '" data-miniprogram-path="' . $keywordReplyData['link_url'] . '?video_id=' . $keywordReplyData['video_id'] . '&official_account_id=' . $data['id'] . '&uid=' . $uid . '">' . $keywordReplyData['title'] . '</a>

' . $keywordReplyData['down_text'];
            } else if ($keywordReplyData['type'] == 2) {
                // 小程序卡片
                $accessToken = Common::getWechatOfficialAccountAccessToken($data['app_id'], $data['app_secret'], $data['id']);

                if (!$accessToken) {
                    $content = Error::ACCESS_TOKEN_NOT_EXISTS;
                    return sprintf($textTpl, $openId, $toUsername, $time, $msgtype, $content);
                }

                $url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=' . $accessToken; // 发卡片消息

                $requestData = '{
                    "touser":"' . $openId . '",
                    "msgtype":"miniprogrampage",
                    "miniprogrampage":
                    {
                        "title":"' . $keywordReplyData['title'] . '",
                        "appid":"' . $appletData['app_id'] . '",
                        "pagepath":"' . config('enum.watch_video_link_jump_url') . '&video_id=' . $keywordReplyData['video_id'] . '&official_account_id=' . $data['id'] . '&uid=' . $uid . '",
                        "thumb_media_id":"' . $keywordReplyData['media_id'] . '"
                    }
                }';

                $result = Common::curlRequest($url, 'POST', $requestData);

                if (!empty($result['errcode'])) {
                    return sprintf($textTpl, $openId, $toUsername, $time, $msgtype, $result['errmsg']);
                }

                $content = '回复“8”每日领取K币';
            }
        }

        return sprintf($textTpl, $openId, $toUsername, $time, $msgtype, $content);
    }

    // 添加订阅消息提醒
    public function addSubscriptionMessageReminder($videoId, $uid)
    {
        $userData = $this->userModel->getInfoById($uid, 'official_account_open_id, official_account_id, status');

        if (empty($userData['status'])) {

            $officialAccountData = $this->officialAccountsModel->getInfoById($userData['official_account_id'], 'id, app_id, app_secret');

            // 获取生成关注公众号的二维码链接
            $url = Common::getOfficialAccountFollowQrcodeUrl($officialAccountData, $uid);

            if (!$url) {
                return [];
            }

            $qrcodeUrl = Common::createQrcode($url, 'uploads/qrcode/' . $uid . '.png', 8);

            if (!$qrcodeUrl) {
                return [];
            }

            return ['url' => $qrcodeUrl];
        }

        $data = $this->subscribeRecordModel->getInfoByWhere(['uid' => $uid, 'video_id' => $videoId], 'id');
        if ($data) {
            return ['url' => ''];
        }
        $this->subscribeRecordModel->edit([
            'uid' => $uid,
            'video_id' => $videoId,
            'open_id' => $userData['official_account_open_id'],
            'created_at' => time(),
            'official_account_id' => $userData['official_account_id']
        ]);

        return ['url' => ''];
    }

    // 自定义菜单的click事件
    public function menuClickEvent($postObj)
    {
        $openId = (string)$postObj->FromUserName;
        $eventKey = (string)$postObj->EventKey;
        $toUsername = (string)$postObj->ToUserName;
        $time = time();
        $msgtype = 'text';

        $textTpl = '<xml>
            <ToUserName><![CDATA[%s]]></ToUserName>
            <FromUserName><![CDATA[%s]]></FromUserName>
            <CreateTime>%s</CreateTime>
            <MsgType><![CDATA[%s]]></MsgType>
            <Content><![CDATA[%s]]></Content>
            </xml>';

        $data = $this->officialAccountsModel->getInfoByWhere(['original_id' => $toUsername], 'id, name, content, original_id, token, app_id, app_secret, encoding_aes_key, applet_id, sign_reply_switch');

        // 查询用户
        $where = [
            'official_account_open_id' => $openId,
            'applet_id' => $data['applet_id']
        ];
        $userInfo = $this->userModel->getDataByWhere($where, 'id, status, interpolate_id, money');

        $uid = $userInfo['id'];
        $appletData = $this->appletModel->getInfoById($data['applet_id'], 'app_id');

        if (strstr($eventKey, '签到')) {
            if ($data['sign_reply_switch'] == 1) {
                $customReplyData = $this->customReplyModel->getInfoByWhere(['official_account_id' => $data['id'], 'type' => 2], 'up_text, down_text, title, link_url');

                if ($customReplyData) {
                    // 设置了自定义使用自定义
                    $content = $customReplyData['up_text'] . '

<a data-miniprogram-appid="' . $appletData['app_id'] . '" data-miniprogram-path="' . $customReplyData['link_url'] . '?official_account_id=' . $data['id'] . '&uid=' . $uid . '">' . $customReplyData['title'] . '</a>

' . $customReplyData['down_text'];
                } else {
                    // 否则使用默认
                    $content = '<a data-miniprogram-appid="' . $appletData['app_id'] . '" data-miniprogram-path="' . config('enum.sign_link_jump_url') . '?official_account_id=' . $data['id'] . '&uid=' . $uid . '">点击进入签到页面</a>';
                }
            } else {
                // 关闭自动回复
                $content = '';
            }
        } else if (strstr($eventKey, '8')) {
            // 回复“8”每日领取K币
            $redis = Cache::store('redis')->handler();
            $redisKey = config('redis_key.today_k_coin_collection_record_key') . $uid;
            $redisValue = $redis->get($redisKey);

            if ($redisValue) {
                $content = '今日已领取过福利，请<a data-miniprogram-appid="' . $appletData['app_id'] . '" data-miniprogram-path="' . config('enum.sign_link_jump_url') . '?official_account_id=' . $data['id'] . '&uid=' . $uid . '">跳转小程序</a>查看';
            } else {
                // 领取 15 K币
                $bool = $this->userModel->edit(['id' => $uid, 'money' => intval($userInfo['money']) + 15]);
                if ($bool) {
                    // 领取记录存储到redis
                    $endTime = strtotime(date('Y-m-d 23:59:59'));
                    $redis->set($redisKey, (string)time(), $endTime - time());
                    $content = '今日福利已领取成功，获得15K币';
                } else {
                    $content = '今日福利领取失败~';
                }
            }
        }

        return sprintf($textTpl, $openId, $toUsername, $time, $msgtype, $content);
    }

    // 获取跳转小程序的链接
    public function getJumpWxAppletUrl($params)
    {
        $linkId = $params['link_id'];

        // 存储该链接当日点击数
        $redis = Cache::store('redis')->handler();
        $redis->select(5);
        $redisKey = config('redis_key.link_same_day_click_num') . date('Ymd') . '-' . $linkId;
        $clickNum = $redis->get($redisKey);
        if ($clickNum) {
            $redis->incr($redisKey);
        } else {
            $redis->set($redisKey, 1, 93600); // 26小时 后面两小时用于定时任务获取点击数
        }

        $linkData = $this->linkModel->getInfoByWhere(['id' => $linkId], 'official_account_id');
        $officialAccountData = $this->officialAccountsModel->getInfoByWhere(['id' => $linkData['official_account_id']], 'applet_id');
        $appletData = $this->appletModel->getInfoById($officialAccountData['applet_id'], 'id, name, url, app_id, app_secret');

        // 获取微信accessToken
        $accessToken = Common::getWechatAppletAccessToken($appletData['app_id'], $appletData['app_secret'], $appletData['id']);

        if (empty($accessToken)) {
            return ['message' => Error::ACCESS_TOKEN_NOT_EXISTS];
        }

        // 获取微信小程序页面链接
        $getUrlLink = 'https://api.weixin.qq.com/wxa/generate_urllink?access_token=' . $accessToken;
        $expireTime = time() + 30 * 86400;

        if (!empty($params['ip']) && !empty($params['ua'])) {
            $query = 'video_id=' . $params['video_id'] . '&video_detail_id=' . $params['video_detail_id'] . '&link_id=' . $linkId . '&ip=' . $params['ip'] . '&ua=' . $params['ua'] . '&source=link';
        } else {
            $query = 'video_id=' . $params['video_id'] . '&video_detail_id=' . $params['video_detail_id'] . '&link_id=' . $linkId . '&source=link';
        }

        if (!empty($params['type'])) {
            // 快手/百度
            $query = $query . '&type=' . $params['type'];
        }

        if (!empty($params['type']) && $params['type'] == 3) {
            file_put_contents('./baidu_monitoring_link.txt', date('Y-m-d H:i:s') . ' ：' . json_encode($params) . ';' . PHP_EOL, FILE_APPEND);
        }

        $requestData = [
            'path' => $appletData['url'],
            'query' => $query,
            'expire_type' => 0,
            'expire_time' => $expireTime
        ];

        $result = Common::curlRequest($getUrlLink, 'POST', json_encode($requestData));
        $result = json_decode($result, true);

        if ($result['errcode']) {
            return ['message' => $result['errmsg']];
        }

        $result['applet_name'] = $appletData['name'];
        return $result;
    }

    /**
     * 获取用户信息
     * @param $uid
     * @return mixed
     */
    public function getUserInfo($uid)
    {
        $redis = Cache::store('redis')->handler();
        $redis->select(8);
        $key = config('redis_key.api.user_login');
        $userInfo = $redis->get($key . $uid);
        return $userInfo ? json_decode($userInfo, true) : null;
    }

    /**
     * 创建订单
     * @param $params
     * @param $userId
     * @return int
     */
    public function createOrder($params,$userId, $payMerchant = PayRecord::PAY_MERCHANT_VIRTUALPAYMENT)
    {
        $name = $params['name'] ?? '测试数据';
        $name = addslashes(strip_tags($name));
        $total = $params['total_fee'] ?? '1';
        $number = $params['number'] ?? 1;
        $type = $params['type'] ?? 1;
        $orderNo =Common::Snowflake();// date('YmdHis') . rand(1000, 10000);

        $userInfo = $this->userModel->getInfoById($userId, 'id, link_id, activation_time, official_account_id, interpolate_id, advertiser_id, applet_id, open_id, mobile_system');

        if (!empty($userInfo['link_id']) && empty($userInfo['advertiser_id'])) {
            // 腾讯未匹配到的投放数据延时到订单匹配
            $linkData = $this->linkModel->getInfoByWhere(['id' => $userInfo['link_id'], 'media_id' => 4], 'id');
            if (!empty($linkData)) {
                $redis = Cache::store('redis')->handler();
                $redis->select(6);
                $monitoringData = $redis->get(md5($userInfo['open_id']));

                if (!empty($monitoringData)) {
                    $monitoringData = json_decode($monitoringData, true);
                    $userSaveData['clickid'] = $monitoringData['click_id'];
                    $userSaveData['advertiser_id'] = $monitoringData['advertiser_id'];
                    $userSaveData['back_url'] = $monitoringData['callback'];
                    $userSaveData['id'] = $userInfo['id'];
                    $this->userModel->edit($userSaveData);
                    $userInfo['advertiser_id'] = $monitoringData['advertiser_id'];
                }
            }
        }

        $saveData = [
            'uid'        => $userId,
            'order_no'   => $orderNo,
            'name'       => $name,
            'type'       => $type,
            'number'     => $number,
            'total_fee'  => round($total / 100, 2),
            'pay_merchant'  => $payMerchant,
            'created_at' => time(),
            'consume_date'    => date('Ymd'),
            'consume_month'   => date('Ym'),
            'link_id'         => !empty($userInfo['link_id']) ? $userInfo['link_id'] : null,
            'activation_time' => !empty($userInfo['activation_time']) ? $userInfo['activation_time'] : null,
            'activation_date' => !empty($userInfo['activation_time']) ? date('Ymd', strtotime($userInfo['activation_time'])) : null,
            'official_account_id' => !empty($userInfo['official_account_id']) ? $userInfo['official_account_id'] : null,
            'interpolate_id' => !empty($userInfo['interpolate_id']) ? $userInfo['interpolate_id'] : null,
            'advertiser_id' => !empty($userInfo['advertiser_id']) ? $userInfo['advertiser_id'] : null,
            'device_type' => WechatService::getDeviceType($userInfo['mobile_system'] ?? ''),
            'activity_id' => $params['activity_id'] ?? 0,
        ];

        if (!empty($params['video_id'])) {
            $saveData['video_id'] = $params['video_id'];
        }
        return $this->payRecordModel->edit($saveData);
    }

    public function queryUserBalance($userId,$appletData)
    {
        $uri = '/xpay/query_user_balance';
        $userInfoRedis = $this->getUserInfo($userId);

        // 获取微信accessToken
        $accessToken = Common::getWechatAppletStableAccessToken($appletData['app_id'], $appletData['app_secret'], $appletData['id']);

        if (empty($accessToken)) {
            return ['message' => Error::ACCESS_TOKEN_NOT_EXISTS];
        }
        // 调用第三方请求参数
        $postBody =  json_encode([
            'openid' => $userInfoRedis['open_id'],
            'user_ip' => Common::getClientIp(),
            'env' => config('app.env') == 'sandbox' ? 1 : 0,//0-正式环境 1-沙箱环境
        ]);
        $appKey = config('app.env') == 'sandbox' ? $appletData['sandbox_key'] : $appletData['prod_key'];
        $getUrlLink = config('enum.wechat_pay_base_url').$uri.'?'.http_build_query([
                'access_token' => $accessToken,
                'pay_sig' => $this->calcPaySig($uri, $postBody, $appKey),
                'signature' => $this->calcSignature($postBody,$userInfoRedis['session_key']),
            ]);
        $result = Common::curlRequest($getUrlLink, 'POST', $postBody);
        $res = json_decode($result, true);
        return  $res['balance'] ?? 0;
    }

    /**
     * 获取客户端系统
     * @param $str
     * @return string
     */
    public static function getPlatform($str): string
    {
        $str = is_string($str) && !empty($str) ? strtolower($str) : '';
        if (preg_match('/android/', $str)) {
            return 'android';
        } elseif (preg_match('/ios/', $str)) {
            return 'ios';
        } else {
            return 'unknown';
        }
    }

    /**
     * 获取设备类型
     * @param $mobile_system
     * @return int|string
     */
    public static function getDeviceType($mobile_system)
    {
        $deviceTypeArr = array_flip(PayRecord::DEVICE_TYPE_ARR);
        return $deviceTypeArr[self::getPlatform($mobile_system)] ?? PayRecord::DEVICE_TYPE_OTHER;
    }

    /**
     * 微信虚拟支付
     * @param $params
     * @param $userId
     * @param int $payMerchant
     * @return array
     */
    public function virtualPayment($params, $userId, int $payMerchant = PayRecord::PAY_MERCHANT_VIRTUALPAYMENT): array
    {
        flog([
            'message' => '小程序虚拟支付参数',
            'user_id' => $userId,
            'params' => $params,
        ],'wechat_pay');
        $totalFee = $params['total_fee'] ?? 0;
        $orderId = $this->createOrder($params,$userId,$payMerchant);
        $payRecord = $this->payRecordModel->getDataByWhere(['id' => $orderId],'id,order_no,name,video_id,link_id');
        $appletData = $this->appletModel->getInfoByWhere(['app_id' => $params['app_id']], 'id, name, url, app_id, app_secret, sandbox_key, prod_key, offer_id');
        $userInfoRedis = $this->getUserInfo($userId);
        $mobileSystem = $userInfoRedis['mobile_system'] ?? '';

        $signData = json_encode([
            'offerId' => $appletData['offer_id'],
            'buyQuantity' => intval($totalFee ?? 1),
            'env' => config('app.env') == 'sandbox' ? 1 : 0,//0-正式环境 1-沙箱环境
            'currencyType' => 'CNY',
            'platform' => self::getPlatform($mobileSystem),
//            'productId' => $payRecord['video_id'] ?? 0,//代币不用传productId
            'goodsPrice' => $totalFee,
            'outTradeNo' => $payRecord['order_no'] ?? '',
            'attach' => $payRecord['name'] ?? '',
        ]);
        $appKey = config('app.env') == 'sandbox' ? $appletData['sandbox_key'] : $appletData['prod_key'];
        $returnData = [
            'signData' => $signData,
            'mode' => self::VIRTUAL_PAYMENT_MODE_SHORT_SERIES_COIN,
            'paySig' => $this->calcPaySig('requestVirtualPayment',$signData,$appKey),
            'signature' => $this->calcSignature($signData,$userInfoRedis['session_key']),
            'is_jump_applet' => 0,
        ];
        flog([
            'message' => '小程序虚拟支付返回给前端数据',
            'returnData' => $returnData,
        ],'wechat_pay');
        return $returnData;
    }

    /**
     * 微信小程序支付回调
     * @param $params
     * @return int|void
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function appletCallback($params)
    {
        try {
            require_once app()->getRootPath().'extend/wx-biz-msg/wxBizMsgCrypt.php';
            $timestamp = $params['timestamp'] ?? '';
            $nonce = $params['nonce'] ?? '';
            $msg_signature = $params['msg_signature'] ?? '';
            $toUserName = $params['ToUserName'] ?? '';
            $encrypt = $params['Encrypt'] ?? '';
            $token = config('enum.wechat_applet_pay_back_token');
            $encodingAesKey = config('enum.wechat_applet_pay_back_encoding_aes_key');
            $appletData = $this->appletModel->getInfoByWhere(['origin_id' => $toUserName], 'id, name, url, app_id, app_secret, sandbox_key, prod_key, offer_id');
            $pc = new WXBizMsgCrypt($token, $encodingAesKey, $appletData['app_id'] ?? '');
            $msg = '';
            $format = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%s]]></Encrypt></xml>";
            $from_xml = sprintf($format, $encrypt);
            $errCode = $pc->decryptMsg($msg_signature, $timestamp, $nonce, $from_xml, $msg);
            //解密成功
            if ($errCode == 0) {
                $decryData = json_decode($msg,true);
                flog([
                    'message' => '微信小程序回调数据解密成功',
                    'decryData' => $decryData,
                ],'wechat_pay');
                $wechatOrderNo = $decryData['WeChatPayInfo']['TransactionId'] ?? '';
                $orderNo = $decryData['OutTradeNo'] ?? '';
                $createTime = $decryData['CreateTime'] ?? '';
                $fromUserName = $decryData['FromUserName'] ?? '';
                $quantity = $decryData['CoinInfo']['Quantity'] ?? 0;
                $event = $decryData['Event'] ?? '';
                switch ($event){
                    //代币支付推送
                    case 'xpay_coin_pay_notify':
                        Db::startTrans();
                        // 加锁
                        $info = Db::name('pay_record')->field('id, number, type, uid, link_id, total_fee, status, advertiser_id, activation_time')->where(['order_no' => $orderNo])->lock(true)->find();
                        if (empty($info) || !empty($info['status'])) {
                            Db::rollback();
                            return 0;
                        }
                        // 是否支付过
                        $isCharge = Db::name('pay_record')->field('id')->where(['uid' => $info['uid'], 'status' => 1])->find();
                        // 修改支付状态
                        $bool = PayRecord::update([
                            'wechat_order_no' => $wechatOrderNo,
                            'status' => 1,
                            'pay_time' => $createTime
                        ],['id' => $info['id']]);
                        if (!$bool) {
                            Db::rollback();
                            return 0;
                        }
                        $userInfo = Db::name('user')->where(['id' => $info['uid']])->field('money, expiration_time, first_charge_date, clickid, interpolate_id, activation_time, ip, ua, applet_id, open_id, back_url')->lock(true)->find();

                        if (in_array($info['type'],[Money::TYPE_COMMON,Money::TYPE_ACTIVITY])) {
                            // 普通充值
                            $saveData = [
                                'money' => $userInfo['money'] + $info['number']
                            ];
                        }elseif ($info['type'] == Money::TYPE_UNLOCK_ALL){
                            $saveData = [
                                //'is_members'      => 0,
                                'updated_at' =>  time(),
                            ];
                        } else {
                            // 会员
                            $expirationTime = self::getMemberExpireTime($info['type'], $userInfo['expiration_time']);

                            $saveData = [
                                'is_members'      => 1,
                                'expiration_time' => $expirationTime
                            ];
                        }

                        if (!$isCharge) {
                            $saveData['first_charge_date'] = time();
                        }

                        // 现有的余额加充值的K币或修改成会员
                        $saveMoneyBool = Db::name('user')->where(['id' => $info['uid']])->update($saveData);

                        if (!$saveMoneyBool) {
                            Db::rollback();
                            return 0;
                        }

                        if (!empty($userInfo['interpolate_id'])) {
                            Db::name('keyword_reply')->where(['id' => $userInfo['interpolate_id']])->inc('order_amount', $info['total_fee'])->update();
                        }
                        if (!empty($info['link_id'])) {
                            $linkData = Db::name('link')->where(['id' => $info['link_id']])->field('recharge_price, echo_rule_config_id')->find();
                            $rechargePrice = $linkData['recharge_price'] + round($info['total_fee'], 2);
                            Db::name('link')->where(['id' => $info['link_id']])->update([
                                'recharge_price' => $rechargePrice
                            ]);
                        }

                        Db::commit();

                        // 根据回传比例回传对应平台
                        if ($info['link_id'] && $userInfo['clickid'] && $info['advertiser_id']) {

                            $link = Db::name('link')->where(['id' => $info['link_id']])->field('is_new_back_setting')->find();

                            $info['clickid'] = $userInfo['clickid'];
                            $info['open_id'] = $userInfo['open_id'];
                            $info['back_url'] = $userInfo['back_url'];
                            $info['applet_id'] = $userInfo['applet_id'];

                            if ($link['is_new_back_setting'] == 1) {
                                $jobQueueName = 'transferBack';
                                $isPushed = Queue::push('app\job\TransferBack', $info, $jobQueueName);
                            } else {
                                $jobQueueName = 'postBack';
                                $isPushed = Queue::push('app\job\PostBack', $info, $jobQueueName);
                            }
                        }
                        break;
                }
                return true;
            } else {
                return false;
            }
        }catch (Exception $e){
            flog([
                'message' => '微信小程序支付回调解密失败',
                'exception_data' => $e->getFile().'-'.$e->getLine().'-'.$e->getMessage(),
            ],'wechat_pay','error');
            return false;
        }
    }

    /**
     * 扣除代币
     * @param $params
     * @return bool
     */
    public function currencyPay($params,$userId)
    {
        $appId = $params['app_id'] ?? 0;

        $userInfo = $this->userModel->getInfoById($userId, 'open_id');
        $appletData = $this->appletModel->getInfoByWhere(['app_id' => $appId], 'id, name, url, app_id, app_secret, sandbox_key, prod_key, offer_id');
        $userInfoRedis = $this->getUserInfo($userId);
        $uri = '/xpay/currency_pay';
        // 获取微信accessToken
        $accessToken = Common::getWechatAppletStableAccessToken($appletData['app_id'], $appletData['app_secret'], $appletData['id']);

        if (empty($accessToken)) {
            return ['message' => Error::ACCESS_TOKEN_NOT_EXISTS];
        }
        $appKey = config('app.env') == 'sandbox' ? $appletData['sandbox_key'] : $appletData['prod_key'];

        // 调用第三方请求参数
        $postBody =  json_encode([
            'openid' => $userInfo['open_id'],
            'user_ip' => Common::getClientIp(),
            'env' => config('app.env') == 'sandbox' ? 1 : 0,//0-正式环境 1-沙箱环境
            'amount' => $params['amount'],
            'order_id' => $params['order_id'],
            'payitem' => [['productid' => '0','unit_price' => '1', 'quantity' => $params['amount']]],
            'remark' => '',
            'device_type' => 1,//平台类型1-安卓 2-苹果
        ]);
        flog([
            'message' => '代币扣除请求参数',
            'postBody' => $postBody,
        ],'wechat_pay');
        $params = [
            'access_token' => $accessToken,
            'pay_sig' => $this->calcPaySig($uri, $postBody,$appKey),
            'signature' => $this->calcSignature($postBody,$userInfoRedis['session_key']),
        ];
        $requestUrl = config('enum.wechat_pay_base_url').$uri.'?'.http_build_query($params);
        $result = Common::curlRequest($requestUrl, 'POST', $postBody);
        $res = json_decode($result, true);
        $errCode = $res['errcode'] ?? 1;
        $balance = $res['balance'] ?? 0;
        flog([
            'message' => '代币扣后余额',
            'balance' => $balance,
        ],'wechat_pay');
        return $errCode == 0;
    }

    /**
     * 校验推送是否来自于微信
     * @param $params
     * @return bool
     */
    public function checkSignature($params)
    {
        $signature = $params["signature"] ?? '';
        $timestamp = $params["timestamp"] ?? '';
        $nonce = $params["nonce"] ?? '';

        $token = config('enum.wechat_applet_pay_back_token');
        $tmpArr = [$token, $timestamp, $nonce];
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
        return $tmpStr == $signature;
    }

    /**
     * pay_sig签名算法计算示例
     * @param $uri
     * @param $postBody
     * @param $appKey
     * @return string
     */
    public function calcPaySig($uri, $postBody, $appKey): string
    {
        $needSignMsg = $uri . '&' . $postBody;
        return hash_hmac('sha256', $needSignMsg, $appKey);
    }

    /**
     * 用户登录态signature签名算法
     * @param $postBody
     * @param $sessionKey
     * @return string
     */
    public function calcSignature($postBody, $sessionKey)
    {
        return hash_hmac('sha256', $postBody, $sessionKey);
    }

    /**
     * 检测是否为true
     * @param $val
     * @param bool $return_null
     * @return bool|mixed
     */
    public function isTrue($val, bool $return_null=false){
        $boolval = ( is_string($val) ? filter_var($val, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) : (bool) $val );
        return ( $boolval===null && !$return_null ? false : $boolval );
    }

    /**
     * 生成随机字符串
     * @param int $length
     * @param string $type
     * @return string
     */
    public static function generateRandomString(int $length, string $type = 'english'): string
    {
        $characters = '';
        if ($type === 'english') {
            $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        } elseif ($type === 'numeric') {
            $characters = '0123456789';
        } elseif ($type === 'mixed') {
            $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        } else {
            return 'Invalid type';
        }

        $str = '';
        $charactersLength = strlen($characters);

        for ($i = 0; $i < $length; $i++) {
            $randomIndex = mt_rand(0, $charactersLength - 1);
            $str .= $characters[$randomIndex];
        }

        return $str;
    }

    /**
     * 生成订单号
     * @return string
     */
    public static function genOrderNo()
    {
        return date('YmdHis') . str_pad(rand(1, 99999999), 8, '0', STR_PAD_LEFT);
    }

    public static function subscribeSend($params)
    {

    }

    //新播放组件返回加密数据
    public static function encryptPlayData($uid, $drama_id, $serial_list): string
    {
        $redis = Cache::store('redis')->handler();
        $redis->select(8);
        $key = config('redis_key.api.user_login');
        $userInfo = $redis->get($key . $uid);
        $userInfo = $userInfo ? json_decode($userInfo, true) : [];
        $session_key = $userInfo['session_key'] ?? '';
        if (empty($session_key)) return '';
        $key = rtrim($session_key, '==');
        $lockDataReq = [
            'openid' => $userInfo['open_id'],
            'src_appid' => 'wx9e795df72358378b', //暂定比翼鸟剧场
            'drama_id' => (string)$drama_id,
            'data_expire_at' => time() + 7200,  //time() + 7200
            'serial_list' => $serial_list
        ];
        $lockDataReqJson = json_encode($lockDataReq);
        return base64_encode(openssl_encrypt($lockDataReqJson, 'aes-128-ecb', $key,1));
    }
}